package cn.edu.xjtu.android.emoji_warfare_android.engine.worker;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.util.Log;
import cn.edu.xjtu.android.emoji_warfare_android.engine.BaseEngine;
import cn.edu.xjtu.android.emoji_warfare_android.engine.Layer;
import cn.edu.xjtu.android.emoji_warfare_android.engine.ScreenBoundary;
import cn.edu.xjtu.android.emoji_warfare_android.engine.entity.Entity;
import cn.edu.xjtu.android.emoji_warfare_android.engine.entity.Point;
import cn.edu.xjtu.android.emoji_warfare_android.widget.CheckDraw;
import cn.edu.xjtu.android.emoji_warfare_android.widget.OnDraw;

/**
 * RenderWorkder<br>
 * 渲染器，用于代替旧的图像引擎<br>
 * 主要用法:<br>
 * 1.将物理边界传入构造器<br>
 * 2.将绘制区域宽度高度传入构造器<br>
 * 3.将渲染时间间隔传入构造器或使用默认<br>
 * 4.将需要绘制的对象用add或addAll加入渲染器<br>
 * 5.覆写可视实体的onDraw方法，返回值为是否需要在绘制完毕后永久忽略(删除)该实体<br>
 * 6.设置绘制完毕的回调(一般为旧图像回收与新图像的加载)<br>
 * 7.使用resume方法启动渲染器<br>
 * 高级用法<br>
 * 使用getBasePoint获取渲染器绘制基点，设计一个循环使这个原点不断震动，以此实现震动效果。<br>
 * 注意：<br>
 * 第5条中所说的“删除”是仅仅在渲染器对象列表中删除，不会对其他处理器造成影响
 * 
 * @author bill
 *
 */
public class RenderWorker extends BaseWorker implements OnDraw {
    /**
     * 默认渲染时间间隔，60fps一般需要16毫秒
     */
    public final static long DEFAULT_RENDER_INTERVAL = 16;
    /**
     * 用于进行图层排序的比较器
     */
    private final static Comparator<Layer> sLayerComparator;
    /**
     * 物理边界<br>
     * 用于在绘制目标超出范围时自动删除
     */
    private final Entity physicsBoundary;
    /**
     * 用于确认是否在绘制范围之内的边界<br>
     * 高级用法，获取该boundary的原点引用，修改原点引用可以做出震动效果。
     */
    private final ScreenBoundary boundary;
    /**
     * 需要渲染的图层
     */
    private ArrayList<Layer> mLayers;

    private CheckDraw mCheckDraw;

    /**
     * 构建一个渲染器
     * 
     * @param engine
     *            引擎
     * @param physicsBoundary
     *            物理引擎边界
     * @param width
     *            绘制范围宽度
     * @param height
     *            绘制范围高度
     * @param interval
     *            渲染时间间隔
     */
    public RenderWorker(BaseEngine engine, Entity physicsBoundary, int width, int height, long interval) {
        super(engine, interval);
        if (physicsBoundary == null) {
            throw new IllegalArgumentException("Physics boundary can't be null or the graphic engine can't run");
        }
        if (width <= 0) {
            throw new IllegalArgumentException("The width must greater than zero");
        }
        if (height <= 0) {
            throw new IllegalArgumentException("The height must greater than zero");
        }
        this.physicsBoundary = physicsBoundary;
        this.boundary = new ScreenBoundary(width, height);
        mLayers = new ArrayList<Layer>();
    }

    /**
     * 构建一个渲染器，使用默认渲染间隔
     * 
     * @param engine
     *            引擎
     * @param physicsBoundary
     *            物理引擎边界
     * @param width
     *            绘制范围宽度
     * @param height
     *            绘制范围高度
     */
    public RenderWorker(BaseEngine engine, Entity physicsBoundary, int width, int height) {
        this(engine, physicsBoundary, width, height, DEFAULT_RENDER_INTERVAL);
    }

    /**
     * 执行一帧渲染
     */
    @SuppressLint("WrongCall") // 这个是安卓的问题，我这里的onDraw和安卓View的onDraw没有关系
    @Override
    public void run() {
        if (mCheckDraw != null) {
            // 检查是否处于绘制状态，如果在绘制状态则进行绘制(如果屏幕关闭则不在绘制状态)
            mCheckDraw.checkDraw();
        } else {
            Log.w("Render", "Check Draw can't be null!");
        }
    }

    /**
     * 绘制所有对象，不一定会执行，仅当处于绘制状态时才进行绘制
     */
    @SuppressLint("WrongCall")
    @Override
    public void onDraw(Canvas canvas) {
        // 将画布移动到原点，可以和震动效果相对应
        canvas.translate(boundary.basePoint.x, boundary.basePoint.y);
        for (int i = 0; i < mLayers.size(); i++) {
            Layer l = mLayers.get(i);
            // 遍历所有图层
            for (int j = 0; j < l.size(); j++) {
                // 遍历图层内所有元素
                Entity e = l.get(j);
                if (boundary.isOverlap(e)) {
                    // 如果在边界内再进行绘制，超出边界不进行绘制
                    float x = e.basePoint.x;
                    float y = e.basePoint.y;
                    canvas.translate(x, y);
                    if (e.onDraw(canvas)) {
                        l.remove(e);
                    }
                    canvas.translate(-x, -y);
                } else if (!physicsBoundary.isOverlap(e)) {
                    // 如果超出物理引擎边界，则删除该实体，不再进行绘制
                    l.remove(e);
                }
            }
        }
        // 移动回左上角
        canvas.translate(-boundary.basePoint.x, -boundary.basePoint.y);
    }

    /**
     * 创建一个新图层，并返回该图层
     * 
     * @param z
     *            图层z坐标(顺序)
     * @return 图层的引用
     */
    public Layer newLayer(int z) {
        Layer l = new Layer(z);
        mLayers.add(l);
        Collections.sort(mLayers, sLayerComparator);
        return l;
    }

    /**
     * 返回渲染器绘制原点<br>
     * 主要用于实现震动等特效
     * 
     * @return 渲染器绘制原点
     */
    public Point getBasePoint() {
        return boundary.basePoint;
    }

    /**
     * 返回图像绘制范围
     * 
     * @return 图像绘制范围
     */
    public ScreenBoundary getScreenBoundary() {
        return boundary;
    }

    /**
     * 获取当前游戏屏幕截图
     * 
     * @return 游戏屏幕截图
     */
    @SuppressLint("WrongCall")
    public Bitmap getScreenShot() {
        Bitmap screen = Bitmap.createBitmap(boundary.getWidth(), boundary.getHeight(), Config.ARGB_8888);
        Canvas canvas = new Canvas(screen);
        onDraw(canvas);
        return screen;
    }

    /**
     * 创建比较器节约资源
     */
    static {
        // 创建图层比较器
        sLayerComparator = new Comparator<Layer>() {
            @Override
            public int compare(Layer lhs, Layer rhs) {
                if (lhs.z < rhs.z) {
                    return -1;
                } else if (lhs.z > rhs.z) {
                    return 1;
                }
                return 0;
            }
        };
    }

    /**
     * 设置绘制面(一般是一个SurfView)
     * 
     * @param checkDraw
     *            可检查绘制对象
     */
    public void setCheckDraw(CheckDraw checkDraw) {
        this.mCheckDraw = checkDraw;
    }

}
